#ifndef __GRAPHICS2D_CPP__
#define __GRAPHICS2D_CPP__
#include <Graphics2d/Graphics2d.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <iostream>
#include <thread>
#include <Graphics2d/Color.h>
#include "Texture2d.hpp"
#include "VertexArray.hpp"
#include "Shader.hpp"
#include <chrono>
#include <random>

struct Geometry
{
public:
    VertexArray *vao;
    VertexBuffer *vbo;
    IndexBuffer *ebo;
};

Geometry Square;
void InitGeometry()
{
    float SquareVertices[] {
        // positions             // texture coords
         0.5f,  0.5f, -0.5f,      1.0f, 1.0f, // top right
         0.5f, -0.5f, -0.5f,      1.0f, 0.0f, // bottom right
        -0.5f, -0.5f, -0.5f,      0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, -0.5f,      0.0f, 1.0f  // top left 
    };

    unsigned int SquareIndices[]
    {
        0, 1, 3,
        1, 2, 3
    };
    Square.vao = new VertexArray;
    Square.vbo = new VertexBuffer;
    Square.ebo = new IndexBuffer;
    Square.vbo->setData(sizeof(SquareVertices), SquareVertices, GL_STATIC_DRAW);
    Square.ebo->setData(sizeof(SquareIndices), SquareIndices, GL_STATIC_DRAW);

    Square.vao->addVBO(*Square.vbo, 0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), 0);
    Square.vao->addVBO(*Square.vbo, 1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3 * sizeof(float)));
    Square.vao->setEBO(*Square.ebo);
}
void DeleteGeometry()
{
    delete Square.vao;
    delete Square.vbo;
    delete Square.ebo;
}

#define TO_STRING(S) #S

Shader *ImageShader;
void InitShader()
{
    const char *vertexShader = TO_STRING(#version 330 core \n
        layout (location = 0) in vec3 aPos; \n
        layout (location = 1) in vec2 aTexCoord; \n
        out vec2 TexCoord; \n
        uniform mat4 model;\n
        uniform mat4 projection;\n
        void main() \n
        { \n
            gl_Position = projection * model * vec4(aPos, 1.0); \n
            TexCoord = aTexCoord; \n
        }
    );
    const char *fragmentShader = TO_STRING(#version 330 core \n
        out vec4 FragColor; \n
        in vec2 TexCoord; \n
        uniform sampler2D Texture; \n
        uniform vec4 color;\n
        void main() \n
        { \n
            // linearly interpolate between both textures (80% container, 20% awesomeface)
            FragColor = color * texture(Texture, TexCoord); \n
        }
    );
    ImageShader = new Shader(vertexShader, fragmentShader);
}
void DeleteShader()
{
    delete ImageShader;
}

GLFWwindow *G2dWindow;
std::thread *G2dWindowThread;

struct Sprite
{
    G2dImage img;
    float x, y;
    float w, h;
    float r;
    RGBA color;
}G2dCurSprite;
bool isG2dDraw = false;
void G2dDraw()
{
    if(isG2dDraw)
    {
        //draw
        Square.vao->bind();
        ImageShader->use();
        Texture2D tex(G2dCurSprite.img);
        tex.active(0);
        ImageShader->setInt("Texture", 0);
        glm::mat4 model(1.0f);
        model = glm::translate(model, glm::vec3(G2dCurSprite.x + G2dCurSprite.w / 2, G2dCurSprite.y + G2dCurSprite.h / 2, 0.0f));
        model = glm::rotate(model, G2dCurSprite.r * 3.1415927f / 180.0f, glm::vec3(0.0f, 0.0f, 0.5f));
        model = glm::scale(model, glm::vec3(G2dCurSprite.w, G2dCurSprite.h, 1.0f));
        ImageShader->setMat4("model", model);
        glm::mat4 projection(1.0f);
        int WindowWidth, WindowHeight;
        glfwGetWindowSize(G2dWindow, &WindowWidth, &WindowHeight);
        projection = glm::ortho(0.0f, (float)WindowWidth, (float)WindowHeight, 0.0f, -1.0f, 1.0f);
        ImageShader->setMat4("projection", projection);
        ImageShader->setVec4("color", glm::vec4(G2dCurSprite.color.r, G2dCurSprite.color.g, G2dCurSprite.color.b, G2dCurSprite.color.a));
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        isG2dDraw = false;
    }
}

bool isG2dClearColorBuffer = false;
RGBA G2dBgColor = {0.0f, 0.0f, 0.0f, 1.0f};
void G2dClearColorBuffer()
{
    if(isG2dClearColorBuffer)
    {
        glClearColor(G2dBgColor.r, G2dBgColor.g, G2dBgColor.b, G2dBgColor.a);
        glClear(GL_COLOR_BUFFER_BIT);
        isG2dClearColorBuffer = false;
    }
}

bool isG2dSwapBuffers = false;
void G2dSwapBuffers()
{
    if(isG2dSwapBuffers)
    {
        //while(isG2dDraw == true);
        glfwSwapBuffers(G2dWindow);
        isG2dSwapBuffers = false;
    }
}

static bool isG2dLoadTexture = false;
const char *G2dCurLoadImageName = "";
G2dImage G2dOutImage = 0;
void G2dLoadTexture()
{
    if(isG2dLoadTexture)
    {
        Image img(G2dCurLoadImageName);
        Texture2D tex;
        tex.setImage(img);
        img.Delete();
        G2dOutImage = tex.getID();
        
        isG2dLoadTexture = false;
    }
}

void G2dWindowRunning()
{
    while(!glfwWindowShouldClose(G2dWindow))
    {
        G2dLoadTexture();
        G2dClearColorBuffer();
        G2dDraw();
        G2dSwapBuffers();
        glfwPollEvents();
    }
    DeleteGeometry();
    DeleteShader();
    glfwTerminate();
}

void InitG2dWindow(int width, int height, const char *title, bool resizeable)
{
    //init window and opengl
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, resizeable ? GL_TRUE : GL_FALSE);
    G2dWindow = glfwCreateWindow(width, height, title, NULL, NULL);
    if (G2dWindow == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return ;
    }
    glfwMakeContextCurrent(G2dWindow);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return ;
    }
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glViewport(0, 0, width, height);
    
    glfwSetFramebufferSizeCallback(G2dWindow, [](GLFWwindow* window, int width, int height){
        glViewport(0, 0, width, height);
    });

    glfwSetMouseButtonCallback(G2dWindow, [](GLFWwindow* window, int button, int action, int mods){
        
    });

    //init geometry and shader
    InitGeometry();
    InitShader();

    //run
    G2dWindowRunning();
}

void G2dInit(int width, int height, const char *title, bool resizeable)
{
    G2dWindowThread = new std::thread(InitG2dWindow, width, height, title, resizeable);
}

void G2dWaitClose()
{
    G2dWindowThread->join();
}

void G2dClose()
{
    glfwTerminate();
}

bool G2dWindowShouldClose()
{
    return glfwWindowShouldClose(G2dWindow);
}

void G2dSetBackgroundColor(float r, float g, float b, float a)
{
    G2dBgColor.r = r;
    G2dBgColor.g = g;
    G2dBgColor.b = b;
    G2dBgColor.a = a;
}

void G2dClearColor()
{
    isG2dClearColorBuffer = true;
    while(isG2dClearColorBuffer == true)
    {
        std::cout << "";
    }
}

void G2dSwapFrame()
{
    isG2dSwapBuffers = true;
    while(isG2dSwapBuffers == true)
    {
        std::cout << "";
    }
}

G2dImage G2dLoadImage(const char *filename)
{
    G2dCurLoadImageName = filename;

    isG2dLoadTexture = true;
    while(isG2dLoadTexture == true)
    {
        std::cout << "";
    }
    

    return G2dOutImage;
}

void G2dPutImage(G2dImage img, float x, float y, float w, float h, float r, RGBA color)
{
    G2dCurSprite.img = img;
    G2dCurSprite.x = x;
    G2dCurSprite.y = y;
    G2dCurSprite.w = w;
    G2dCurSprite.h = h;
    G2dCurSprite.color = color;
    isG2dDraw = true;
    while(isG2dDraw == true)
    {
        std::cout << "";
    }
}

G2dPoint G2dGetMousePos()
{
    double xpos, ypos;
    glfwGetCursorPos(G2dWindow, &xpos, &ypos);
    return { xpos, ypos };
}

G2dPoint G2dGetWindowSize()
{
    int WindowWidth, WindowHeight;
    glfwGetWindowSize(G2dWindow, &WindowWidth, &WindowHeight);
    return { (float)WindowWidth, (float)WindowHeight };
}

G2dMKState G2dGetMouseState(G2dMouseButton bt)
{
    return (G2dMKState)glfwGetMouseButton(G2dWindow, bt);
}
#endif